Skip to content

Add span links support and BaggageBuilder.invokeAgentServer method#224

Merged
fpfp100 merged 7 commits intomainfrom
feature/span-links-and-baggage-server
Mar 26, 2026
Merged

Add span links support and BaggageBuilder.invokeAgentServer method#224
fpfp100 merged 7 commits intomainfrom
feature/span-links-and-baggage-server

Conversation

@fpfp100
Copy link
Copy Markdown
Contributor

@fpfp100 fpfp100 commented Mar 25, 2026

Summary

  • Add optional spanLinks?: Link[] to SpanDetails interface and thread through all scope classes (OpenTelemetryScope, InvokeAgentScope, InferenceScope, ExecuteToolScope, OutputScope) to support OTel span link propagation for causal relationships between spans
  • Add invokeAgentServer(address, port?) to BaggageBuilder for setting server address/port baggage with proper cleanup of stale port entries when port is 443 or omitted
  • Refactor base scope constructor — Consolidate OpenTelemetryScope constructor from 9 positional parameters to 6 by accepting SpanDetails and UserDetails as structured objects. Subclasses now pass spanDetails through to super() without destructuring individual fields. Adding new span options only requires changes to SpanDetails and the base class.
  • Update CHANGELOG, design docs, and basic-agent-sdk-sample

Console Exporter Output (from basic-agent-sdk-sample)

invoke_agent span — 3 span links + server.address/port
{
  traceId: 'd2b5e95982666984270eacef8284504f',
  name: 'invoke_agent',
  id: 'ecc7dae592a30329',
  kind: 2,
  attributes: {
    'gen_ai.operation.name': 'invoke_agent',
    'microsoft.tenant.id': 'badf1f56-284d-4dc5-ac59-0dd53900e743',
    'gen_ai.agent.id': 'a70248c0-9daa-4b4a-a498-b3c95c8422a5',
    'gen_ai.agent.name': 'Perplexity02 Agent',
    'user.id': '03f4dd93-7e1e-41d6-bf7c-f211f9e96a13',
    'user.name': 'Josjoy',
    'microsoft.channel.name': 'msteams',
    'server.address': 'http://localhost:56150/_connector',
    'server.port': 56150,
    'gen_ai.output.messages': '["LLM Response: Based on my analysis of \\"what can you do\\"...","Tool Response: Email sent successfully to user@example.com"]'
  },
  links: [
    {
      context: {
        traceId: '0aa4621e5ae09963a3de354f3d18aa65',
        spanId: 'c1aaa519600b1bf0',
        traceFlags: 1
      },
      attributes: { 'link.type': 'causal', 'link.reason': 'upstream_request' }
    },
    {
      context: {
        traceId: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
        spanId: 'aaaaaaaaaaaaaaaa',
        traceFlags: 0
      },
      attributes: { 'link.type': 'follows_from', 'link.reason': 'retry' }
    },
    {
      context: {
        traceId: 'cccccccccccccccccccccccccccccccc',
        spanId: 'dddddddddddddddd',
        traceFlags: 1
      },
      attributes: {
        'link.type': 'causal',
        'link.reason': 'parent_orchestrator',
        'link.index': 0
      }
    }
  ]
}
Chat gpt-4 span — 2 span links
{
  traceId: 'd2b5e95982666984270eacef8284504f',
  parentSpanContext: { spanId: 'ecc7dae592a30329' },
  name: 'Chat gpt-4',
  id: 'c054f6cbcd9ef11e',
  kind: 2,
  attributes: {
    'gen_ai.operation.name': 'Chat',
    'gen_ai.request.model': 'gpt-4',
    'gen_ai.provider.name': 'openai',
    'gen_ai.usage.input_tokens': 52,
    'gen_ai.usage.output_tokens': 85,
    'gen_ai.response.finish_reasons': [ 'stop' ],
    'gen_ai.input.messages': '["Analyze the following compliance query: what can you do"]',
    'gen_ai.output.messages': '["Based on my analysis of \\"what can you do\\"..."]'
  },
  links: [
    {
      context: {
        traceId: '0aa4621e5ae09963a3de354f3d18aa65',
        spanId: 'c1aaa519600b1bf0',
        traceFlags: 1
      },
      attributes: { 'link.type': 'causal', 'link.reason': 'upstream_inference' }
    },
    {
      context: {
        traceId: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
        spanId: 'ffffffffffffffff',
        traceFlags: 1
      },
      attributes: { 'link.type': 'follows_from', 'link.reason': 'prompt_cache_hit' }
    }
  ]
}
execute_tool send-email span — 1 span link
{
  traceId: 'd2b5e95982666984270eacef8284504f',
  parentSpanContext: { spanId: 'ecc7dae592a30329' },
  name: 'execute_tool send-email',
  id: 'd32f71bd37979452',
  kind: 0,
  attributes: {
    'gen_ai.operation.name': 'execute_tool',
    'gen_ai.tool.name': 'send-email',
    'gen_ai.tool.call.arguments': '{"recipient":"user@example.com","subject":"Hello","body":"Test email"}',
    'gen_ai.tool.type': 'function',
    'gen_ai.tool.call.result': 'Email sent successfully to user@example.com'
  },
  links: [
    {
      context: {
        traceId: '0aa4621e5ae09963a3de354f3d18aa65',
        spanId: 'c1aaa519600b1bf0',
        traceFlags: 1
      },
      attributes: { 'link.type': 'causal', 'link.reason': 'tool_dispatch' }
    }
  ]
}

New features in the output:

Feature Where it appears
Span links (list) invoke_agent has 3 links, Chat has 2 links, execute_tool has 1 link — each with typed attributes
server.address / server.port On invoke_agent span — set via BaggageBuilder.invokeAgentServer()
Port omission for 443 Inference span uses port 443 (OpenAI default) — correctly omitted

Test plan

  • Verify span links are correctly forwarded to OTel spans on all scope types (InvokeAgentScope, InferenceScope, ExecuteToolScope, OutputScope)
  • Verify span links with attributes are preserved
  • Verify omitted spanLinks results in empty links array
  • Verify invokeAgentServer sets address baggage and omits port for 443/undefined
  • Verify stale port baggage is cleared on subsequent calls with port=443
  • Basic sample runs with ConsoleSpanExporter and outputs spans with link lists

🤖 Generated with Claude Code

@fpfp100 fpfp100 requested review from a team as code owners March 25, 2026 17:46
Copilot AI review requested due to automatic review settings March 25, 2026 17:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds OpenTelemetry span link propagation across observability scope types and extends the baggage fluent API to set server.address / server.port for invoke-agent scenarios.

Changes:

  • Add optional spanLinks?: Link[] to OpenTelemetryScope and all derived scope start(...) APIs, wiring through to startSpan({ links }).
  • Add BaggageBuilder.invokeAgentServer(address, port?) with logic to omit/clear server.port for 443/undefined.
  • Add/adjust tests and update design docs + changelog to reflect the new APIs.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/observability/core/span-links.test.ts New tests asserting Link[] are forwarded into created spans.
tests/observability/core/BaggageBuilder.test.ts Adds tests for the new invokeAgentServer fluent setter.
packages/agents-a365-observability/src/tracing/scopes/OpenTelemetryScope.ts Accepts spanLinks?: Link[] and forwards to OTel startSpan options.
packages/agents-a365-observability/src/tracing/scopes/InvokeAgentScope.ts Threads spanLinks through start(...) and super(...).
packages/agents-a365-observability/src/tracing/scopes/InferenceScope.ts Threads spanLinks through start(...) and super(...).
packages/agents-a365-observability/src/tracing/scopes/ExecuteToolScope.ts Threads spanLinks through start(...) and super(...).
packages/agents-a365-observability/src/tracing/scopes/OutputScope.ts Threads spanLinks through start(...) and super(...).
packages/agents-a365-observability/src/tracing/middleware/BaggageBuilder.ts Implements invokeAgentServer(address, port?) and port cleanup behavior.
packages/agents-a365-observability/docs/design.md Documents span link support and the new baggage builder method.
CHANGELOG.md Records the new APIs under “Added”.

jsl517 and others added 2 commits March 25, 2026 13:16
)

Add optional `spanLinks` parameter to all scope classes (OpenTelemetryScope,
InvokeAgentScope, InferenceScope, ExecuteToolScope, OutputScope) to support
OTel span link propagation. Add `invokeAgentServer()` to BaggageBuilder for
setting server address/port baggage with proper cleanup of stale port entries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move jest.spyOn console mocks from beforeAll to beforeEach (restoreMocks
  compatibility)
- Remove trace.disable() from afterAll to avoid resetting global provider
  for other test suites
- Add test for clearing previously set non-443 port when invokeAgentServer
  is called again with port=443

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 25, 2026 20:17
@fpfp100 fpfp100 force-pushed the feature/span-links-and-baggage-server branch from ee4f0a6 to 1e81f01 Compare March 25, 2026 20:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

…ryScope constructor

Update the base OpenTelemetryScope constructor signature from 9 positional parameters to 6 by accepting SpanDetails and UserDetails as structured objects. This encapsulates span configuration in the base class so subclasses simply pass spanDetails through without destructuring. Adding new span options now only requires changes to SpanDetails and the base class.
juliomenendez
juliomenendez previously approved these changes Mar 26, 2026
nikhilNava
nikhilNava previously approved these changes Mar 26, 2026
Copy link
Copy Markdown
Contributor

@nikhilNava nikhilNava left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you do the same for span kind in the base scope

Move the `kind` parameter from a standalone constructor argument into
the `SpanDetails` object in OpenTelemetryScope. Each subclass now builds
a `resolvedSpanDetails` with its required span kind before calling super().
Simplify the `spanLinks` JSDoc in contracts.ts and clarify that the base
class defaults spanKind to SpanKind.CLIENT.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 26, 2026 18:53
@fpfp100 fpfp100 dismissed stale reviews from nikhilNava and juliomenendez via 6ae6387 March 26, 2026 18:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

ExecuteToolScope always forces SpanKind.INTERNAL regardless of any
spanKind override in SpanDetails. Update the test expectation to
verify the override is correctly ignored.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@fpfp100
Copy link
Copy Markdown
Contributor Author

fpfp100 commented Mar 26, 2026

Can you do the same for span kind in the base scope

fixed .

juliomenendez
juliomenendez previously approved these changes Mar 26, 2026
Copilot AI review requested due to automatic review settings March 26, 2026 19:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

@fpfp100 fpfp100 merged commit a110fee into main Mar 26, 2026
11 checks passed
@fpfp100 fpfp100 deleted the feature/span-links-and-baggage-server branch March 26, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants